unit QBOracleWindow;
{$I QBuilderVerCtrl.inc}
interface

uses
  {$IFDEF VCL16}
    System.Classes,
    Vcl.Controls,
    Vcl.StdCtrls,
    Vcl.ComCtrls,
    Winapi.Windows,
  {$ELSE}
    Classes,
    Controls,
    StdCtrls,
    ComCtrls,
    Windows,
  {$ENDIF}
  QBWindow,
  QBParser,
  QBStringTypes;

const
  OracleSQLKeywords =
    'ACCESS,ACCOUNT,ACTIVATE,ADD,ADMIN,AFTER,ALL,ALL_ROWS,ALLOCATE,ALTER,' +
    'ANALYZE,AND,ANY,ARCHIVE,ARCHIVELOG,ARRAY,AS,ASC,ASCENDING,AT,AUDIT,' +
    'AUTHENTICATED,AUTHORIZATION,AUTOEXTEND,AUTOMATIC,BACKUP,BECOME,BEFORE,' +
    'BEGIN,BETWEEN,BFILE,BITMAP,BLOB,BLOCK,BODY,BY,CACHE,CACHE_INSTANCES,' +
    'CANCEL,CASCADE,CAST,CFILE,CHAINED,CHANGE,CHAR,CHAR_CS,CHARACTER,' +
    'CHARACTERS,CHECK,CHECKPOINT,CHOOSE,CHUNK,CLEAR,CLOB,CLONE,CLOSE,' +
    'CLOSED_CACHED_OPEN_,CLUSTER,COALESCE,COLLATE,COLLATION,COLUMN,COLUMNS,' +
    'COMMENT,COMMIT,COMMITTED,COMPATIBILITY,COMPILE,COMPLETE,' +
    'COMPOSITE_LIMIT,COMPRESS,COMPUTE,COMPUTED,CONNECT,CONNECT_TIME,' +
    'CONSTRAINT,CONSTRAINTS,CONTENTS,CONTINUE,CONTROLFILE,CONVERT,COST,' +
    'CPU_PER_CALL,CPU_PER_SESSION,CREATE,CURRENT,CURRENT_SCHEMA,' +
    'CURRENT_USER,CURSOR,CURSORS,CYCLE,DANGLING,DATABASE,DATAFILE,' +
    'DATAFILES,DATAOBJNO,DATE,DAY,DBA,DEALLOCATE,DEBUG,DEC,DECIMAL,DECLARE,' +
    'DEFAULT,DEFERRABLE,DEFERRED,DEGREE,DELETE,DEREF,DESC,DESCENDING,' +
    'DIRECTORY,DISABLE,DISCONNECT,DISMOUNT,DISTINCT,DISTRIBUTED,DML,DOUBLE,' +
    'DROP,DUMP,EACH,ELSE,ENABLE,END,ENFORCE,ENTRY,ESCAPE,ESTIMATE,EVENTS,' +
    'EXCEPTIONS,EXCHANGE,EXCLUDING,EXCLUSIVE,EXECUTE,EXEMPT,EXISTS,EXPIRE,' +
    'EXPLAIN,EXTENT,EXTENTS,EXTERNALLY,FAILED_LOGIN_ATTEMPTS,FALSE,FAST,' +
    'FILE,FILTER,FIRST,FIRST_ROWS,FLAGGER,FLOAT,FLUSH,FOR,FORCE,FOREIGN,' +
    'FREELIST,FREELISTS,FROM,FULL,FUNCTION,GLOBAL,GLOBAL_NAME,GLOBALLY,' +
    'GRANT,GROUP,GROUPS,HASH,HASHKEYS,HAVING,HEADER,HEAP,IDENTIFIED,' +
    'IDLE_TIME,IF,IMMEDIATE,IN,INCLUDING,INCREMENT,IND_PARTITION,INDEX,' +
    'INDEXED,INDEXES,INDICATOR,INITIAL,INITIALLY,INITRANS,INNER,INSERT,' +
    'INSTANCE,INSTANCES,INSTEAD,INT,INTEGER,INTERMEDIATE,INTERSECT,INTO,IS,' +
    'ISOLATION,ISOLATION_LEVEL,JOIN,KEEP,KEY,KILL,LAYER,LEFT,LENGTH,LESS,' +
    'LEVEL,LIBRARY,LIKE,LIMIT,LINK,LIST,LOB,LOCAL,LOCK,LOG,LOGFILE,LOGGING,' +
    'LOGICAL_READS_PER_,LOGICAL_READS_PER_CALL,LONG,MANAGE,MASTER,' +
    'MAXARCHLOGS,MAXDATAFILES,MAXEXTENTS,MAXINSTANCES,MAXLOGFILES,' +
    'MAXLOGHISTORY,MAXLOGMEMBERS,MAXSIZE,MAXTRANS,MAXVALUE,MEMBER,' +
    'MINEXTENTS,MINIMUM,MINUS,MINVALUE,MODE,MODIFY,MONTH,MOUNT,MOVE,' +
    'MTS_DISPATCHERS,MULTISET,NATIONAL,NCHAR,NCHAR_CS,NCLOB,NEEDED,NESTED,' +
    'NETWORK,NEW,NEXT,NLS_CALENDAR,NLS_CHARACTERSET,NLS_ISO_CURRENCY,' +
    'NLS_LANGUAGE,NLS_NUMERIC_,NLS_SORT,NLS_TERRITORY,NOARCHIVELOG,NOAUDIT,' +
    'NOCACHE,NOCOMPRESS,NOCYCLE,NOFORCE,NOLOGGING,NOMAXVALUE,NOMINVALUE,' +
    'NONE,NOORDER,NOOVERIDE,NOPARALLEL,NORESETLOGS,NOREVERSE,NORMAL,' +
    'NOS_SPECIAL_CHARS,NOSORT,NOT,NOTHING,NOWAIT,NULL,NUMBER,NUMERIC,' +
    'NVARCHAR2,OBJECT,OBJNO,OBJNO_REUSE,OF,OFF,OFFLINE,OID,OIDINDEX,OLD,ON,' +
    'ONLINE,ONLY,OPCODE,OPEN,OPTIMAL,OPTIMIZER_GOAL,OPTION,OR,ORDER,' +
    'ORGANIZATION,OUTER,OVERFLOW,OWN,PACKAGE,PARALLEL,PARTITION,PASSWORD,' +
    'PASSWORD_GRACE_TIME,PASSWORD_LIFE_TIME,PASSWORD_LOCK_TIME,' +
    'PASSWORD_REUSE_MAX,PASSWORD_REUSE_TIME,PASSWORD_VERIFY_,PCTFREE,' +
    'PCTINCREASE,PCTTHRESHOLD,PCTUSED,PCTVERSION,PERCENT,PERMANENT,PLAN,' +
    'PLSQL_DEBUG,POST_TRANSACTION,PRECISION,PRESERVE,PRIMARY,PRIOR,PRIVATE,' +
    'PRIVATE_SGA,PRIVILEGE,PRIVILEGES,PROCEDURE,PROFILE,PUBLIC,PURGE,QUEUE,' +
    'QUOTA,RANGE,RAW,RBA,READ,REAL,REBUILD,RECOVER,RECOVERABLE,RECOVERY,' +
    'REF,REFERENCES,REFERENCING,REFRESH,RENAME,REPLACE,RESET,RESETLOGS,' +
    'RESIZE,RESOURCE,RESTRICTED,RETURN,RETURNING,REUSE,REVERSE,REVOKE,' +
    'RIGHT,ROLE,ROLES,ROLLBACK,ROW,ROWLABEL,ROWNUM,ROWS,RULE,SAMPLE,' +
    'SAVEPOINT,SCAN_INSTANCES,SCHEMA,SCN,SCOPE,SD_ALL,SD_INHIBIT,SD_SHOW,' +
    'SEG_BLOCK,SEG_FILE,SEGMENT,SELECT,SEQUENCE,SERIALIZABLE,SESSION,' +
    'SESSION_CACHED_,SESSIONS_PER_USER,SET,SHARE,SHARED,SHARED_POOL,SHRINK,' +
    'SIZE,SKIM_UNUSABLE_INDEXES,SMALLINT,SNAPSHOT,SOME,SORT,SPECIFICATION,' +
    'SPLIT,SQL_TRACE,SQLCODE,SQLERROR,STANDBY,START,STATEMENT_ID,' +
    'STATISTICS,STOP,STORAGE,STORE,STRUCTURE,SUCCESSFUL,SWITCH,SYNONYM,' +
    'SYSDATE,SYSDBA,SYSOPER,SYSTEM,TABLE,TABLES,TABLESPACE,TABLESPACE_NO,' +
    'TABNO,TEMPORARY,THAN,THE,THEN,THREAD,TIME,TIMESTAMP,TO,TOPLEVEL,TRACE,' +
    'TRACING,TRANSACTION,TRANSITIONAL,TRIGGER,TRIGGERS,TRUE,TRUNCATE,' +
    'TX,TYPE,UBA,UID,UNARCHIVED,UNDER,UNDO,UNION,UNIQUE,UNLIMITED,UNLOCK,' +
    'UNRECOVERABLE,UNTIL,UNUSABLE,UNUSED,UPDATABLE,UPDATE,USAGE,USE,USER,' +
    'USING,VALIDATE,VALIDATION,VALUE,VALUES,VARCHAR,VARCHAR2,VARRAY,' +
    'VARYING,VIEW,WHEN,WHENEVER,WHERE,WITH,WITHOUT,WORK,WRITE,XID';

  OracleSQLFunctions =
    'ALL,ANY,AVG,COUNT,MAX,MIN,STDDEV,SUM,VARIANCE,ABS,CEIL,COS,COSH,EXP,FLOOR,LN,' +
    'LOG,MOD,POWER,ROUND,SIGN,SIN,SINGLE,ROW,FUNCTIONS,SINH,SQRT,TAN,TANH,' +
    'TRUNC,ASCII,CHR,CONCAT,INITCAP,INSTR,INSTRB,LENGTH,LENGTHB,LOWER,LPAD,' +
    'LTRIM,NLS_INITCAP,NLS_LOWER,NLS_UPPER,NLSSORT,REPLACE,RPAD,RTRIM,' +
    'TRIM,LTRIM,SOUNDEX,SUBSTR,SUBSTRB,TRANSLATE,UPPER,ADD_MONTHS,DATATYPE,DATE,' +
    'LAST_DAY,MONTHS_BETWEEN,NEW_TIME,NEXT_DAY,SYSDATE,CHARTOROWID,CONVERT,' +
    'HEXTORAW,RAWTOHEX,TO_CHAR,DECODE,CUBE,ROLLUP,ROWNUM';

  OracleSQLArregates = 'AVG,COUNT,MAX,MIN,STDDEV,SUM,VARIANCE';

  OracleSQLOperations = '+,-,*,/,||';
  OracleSQLComplexOperators = '(+)';
  OracleSQLCompareOperators = '=,!=,<,<=,>,>=," LIKE "';
  OracleSQLJoinOperators = '=,!=,<,<=,>,>=," LIKE "';

  OracleSQLBoolFalse = 'False';
  OracleSQLBoolTrue = 'True';

type
  TQBOracleOptions = set of ( qbooUseAnsiJoin );

  TQBOracleJoinDef = class(TQBJoinDef)
  protected
    function GetScript: aqbString; override;
  end;

  TQBOracleJoinDefs = class(TQBJoinDefs)
  private
    FAnsiJoin: Boolean;
  public
    property AnsiJoin: Boolean read FAnsiJoin write FAnsiJoin;
  end;

  TQBOracleSQLOptions = class(TPersistent)
  private
    FLimitRows: integer;
    FParser: TQBSelectParser;
    function GetLimitScript: aqbString;
    function GetDistinctScript: aqbString;
  public
    constructor Create(AParser: TQBSelectParser);
    destructor Destroy; override;
    procedure Assign(Source: TPersistent); override;
    procedure Clear;
    property LimitRows: integer read FLimitRows write FLimitRows;
    property LimitScript: aqbString read GetLimitScript;
    property DistinctScript: aqbString read GetDistinctScript;
    property Parser: TQBSelectParser read FParser;
  end;

  TQBOracleSQLScanner = class(TQBSQLScanner)
  private
    FUseNamespaces: boolean;
  protected
    function GetIdentQuote: aqbChar; override;
    function GetProcessAliases: boolean; override;
//    function IsAlpha(Ch: aqbChar): boolean; override;
    function IsQuote(Ch: aqbChar): boolean; override;
    function IsLegalSymbol(Ch: aqbChar): boolean; override;
    function GetCommaKeywords: aqbString; override;
    function GetCommaFunctions: aqbString; override;
    function GetCommaOperations: aqbString; override;
    function GetCommaQualifiers: aqbString; override;
    function ProcessIdent(const S: aqbString): aqbString; override;
    procedure DefineBooleans; override;
    function GetCommaCompareOperators: aqbString; override;
    function GetCommaComplexOperators: aqbString; override;
    function GetCommaJoinOperators: aqbString; override;
    function GetFormatTokenIdent(const Ident: aqbString;
      const Quoted: boolean): aqbString; override;
  public
    constructor Create; override;
    function IsIdentQuote(Ch: aqbChar): boolean; override;
    function NeedQuotes(const Ident: aqbString): boolean; override;
    property UseNamespaces: boolean read FUseNamespaces write FUseNamespaces
      default true;
  end;

  TQBOracleSelectParser = class(TQBSelectParser)
  private
    FOracleSQLOptions: TQBOracleSQLOptions;
    FAnsiJoin: boolean;
    procedure SetOracleSQLOptions(const Value: TQBOracleSQLOptions);
    procedure SetAnsiJoin(Value: Boolean);
  protected
    function CreateScanner: TQBSQLScanner; override;
    function CreateWhereItems: TQBCriterions; override;
    function GetExpression: aqbString; override;
    procedure DefineOperators; override;
    function GetCommaAggregates: aqbString; override;
    procedure DoParseDistinct; virtual;
    procedure DoParseAppendix; override;
    function IsOracleJoins: boolean; override;
    procedure DoParse; override;
    function GetCaseSensitiveIdentifiers: boolean; override;
    function CreateJoinDefs: TQBJoinDefs; override;
    function NeedParentheses: boolean; override;
    function IsSchemaSupported: boolean; override;
  public
    constructor Create(AParent: TPersistent); override;
    destructor Destroy; override;
    procedure Clear; override;
    function IsFullOuterJoinSupported: boolean; override;
    property OracleSQLOptions: TQBOracleSQLOptions read FOracleSQLOptions
      write SetOracleSQLOptions;
    property AnsiJoin: boolean read FAnsiJoin write SetAnsiJoin;
  end;

  TOracleQueryBuilder = class(TFullQueryBuilder {TCustomQueryBuilder})
  private
    FOracleOptions: TQBOracleOptions;
    procedure SetOracleOptions(const Value: TQBOracleOptions);
    function GetUseNamespaces: boolean;
    procedure SetUseNamespaces(const Value: boolean);
  protected
  public
    constructor Create(AOwner: TComponent); override;
    //igorp
    function DeleteTableDefsAliases(AText: aqbString; var NeedComma: Boolean;
      QB: TCustomQueryBuilder; var NeedJoin: Boolean): aqbString; override;
    //\igop
    function CreateParser(AParent: TPersistent): TQBSelectParser; override;
  published
    property About;
    property Align;
    property Aggregates;
    property Functions;
    property Keywords;
    property Operators;
    property Options;
    property OracleOptions: TQBOracleOptions read FOracleOptions
      write SetOracleOptions;
    property Palette;
    property ScriptFormat;
    property Storage;
    property Style;
    property TabHeight;
    property UseNamespaces: boolean read GetUseNamespaces write SetUseNamespaces
      default true;
    property VisibleTabs;
    property _Version;
    // ayz
    property WorkAreaPopupMenu;
    property TabsPopupMenu;
    // \ayz

    property OnStartDrag;
    property OnDragOver;
    property OnDragDrop;
    property OnEndDrag;
    {$IFNDEF EC_UNICODE}
    property OnGetTableFields;
    property OnParserError;
    {$ELSE}
    property OnGetTableFieldsW;
    property OnParserErrorW;
    {$ENDIF}
    property OnAddRemoveTable;
    property OnAddRemoveLink;
    property OnEnterProcParameter;
  end;

implementation

uses
  {$IFDEF VCL16}
    System.SysUtils,
  {$ELSE}
    SysUtils,
  {$ENDIF}
  {$IFDEF EC_UNICODE}
    ecStrUtils,
  {$ENDIF}
  QBUtils;

const
  sSelect = 'SELECT';
  sLimitFrm = 'FETCH FIRST %d ROWS ONLY';
  sFetch = 'FETCH';
  sFirst = 'FIRST';
  sRow = 'ROW';
  sRows = 'ROWS';
  sOnly = 'ONLY';

  sDistinct = 'DISTINCT';
  sOn = 'ON';

  { TQBOracleSQLScanner }

constructor TQBOracleSQLScanner.Create;
begin
  inherited;
  FUseNamespaces := true;
  EnableNameSpaces := true;
end;

procedure TQBOracleSQLScanner.DefineBooleans;
begin
  Booleans[False] := OracleSQLBoolFalse;
  Booleans[True] := OracleSQLBoolTrue;
end;

function TQBOracleSQLScanner.GetCommaCompareOperators: aqbString;
begin
  Result := OracleSQLCompareOperators;
end;

function TQBOracleSQLScanner.GetCommaComplexOperators: aqbString;
begin
  Result := OracleSQLComplexOperators;
end;

function TQBOracleSQLScanner.GetCommaFunctions: aqbString;
begin
  Result := OracleSQLFunctions;
end;

function TQBOracleSQLScanner.GetCommaJoinOperators: aqbString;
begin
  Result := OracleSQLJoinOperators;
end;

function TQBOracleSQLScanner.GetCommaKeywords: aqbString;
begin
  Result := OracleSQLKeywords;
end;

function TQBOracleSQLScanner.GetCommaOperations: aqbString;
begin
  Result := OracleSQLOperations;
end;

function TQBOracleSQLScanner.GetCommaQualifiers: aqbString;
begin
  Result := EmptyStr;
end;

function TQBOracleSQLScanner.GetFormatTokenIdent(const Ident: aqbString;
  const Quoted: boolean): aqbString;
begin
  Result := inherited GetFormatTokenIdent(Ident, Quoted);
  if not Quoted then
    Result := QBUpperCase(Ident);
end;

function TQBOracleSQLScanner.GetIdentQuote: aqbChar;
begin
  Result := '"';
end;

function TQBOracleSQLScanner.GetProcessAliases: boolean;
begin
  Result := True;
end;

function TQBOracleSQLScanner.IsIdentQuote(Ch: aqbChar): boolean;
begin
  Result := Ch = '"';
end;

function TQBOracleSQLScanner.IsLegalSymbol(Ch: aqbChar): boolean;
begin
  Result := inherited IsLegalSymbol(Ch) or (Ch = '$');
end;

function TQBOracleSQLScanner.IsQuote(Ch: aqbChar): boolean;
begin
  Result := Ch = '''';
end;

function TQBOracleSQLScanner.NeedQuotes(const Ident: aqbString): boolean;
begin
  if (Length(Ident) > 1) and IsIdentQuote(Ident[1]) and
    IsIdentQuote(Ident[Length(Ident)]) then
    Result := false
  else
    Result := IsKeyword(Ident) or IsFunction(Ident) or
      (inherited NeedQuotes(Ident)) or (Ident <> QBUpperCase(Ident));
end;

function TQBOracleSQLScanner.ProcessIdent(const S: aqbString): aqbString;
begin
  if (Length(S) > 1) and IsIdentQuote(S[1]) and IsIdentQuote(S[Length(S)]) and
  (S[1] = S[Length(S)]) then
    Result := Copy(S, 2, Length(S) - 2)
  else
    Result := QBUpperCase(S);
  Result := inherited ProcessIdent(S);
end;

{ TQBOracleSQLOptions }

procedure TQBOracleSQLOptions.Assign(Source: TPersistent);
begin
  if Source is TQBOracleSQLOptions then
  begin
    FLimitRows := TQBOracleSQLOptions(Source).LimitRows;
  end
  else
    inherited;
end;

procedure TQBOracleSQLOptions.Clear;
begin
  FLimitRows := 0;
end;

constructor TQBOracleSQLOptions.Create(AParser: TQBSelectParser);
begin
  inherited Create;
  FParser := AParser;
end;

destructor TQBOracleSQLOptions.Destroy;
begin
  inherited;
end;

function TQBOracleSQLOptions.GetDistinctScript: aqbString;
begin
  Result := '';
  if Assigned(FParser) and FParser.Distinct then
    Result := sDistinct + ' ';
end;

function TQBOracleSQLOptions.GetLimitScript: aqbString;
var
  S: aqbString;
begin
  Result := '';
  if FLimitRows > 0 then
  begin
    S := sLimitFrm;
    if Assigned(FParser) then
      S := FParser.ProcessKeyword(S);
    Result := QBFormat(S, [FLimitRows]);
  end;
  if Result <> '' then
    Result := Result + sEOL;
end;

{ TQBOracleSelectParser }

procedure TQBOracleSelectParser.Clear;
begin
  inherited;
  FOracleSQLOptions.Clear;
end;

constructor TQBOracleSelectParser.Create(AParent: TPersistent);
begin
  inherited Create(AParent);
  FOracleSQLOptions := TQBOracleSQLOptions.Create(Self);
end;

function TQBOracleSelectParser.CreateJoinDefs: TQBJoinDefs;
begin
  Result := TQBOracleJoinDefs.Create(Self, TQBOracleJoinDef);
end;

function TQBOracleSelectParser.CreateScanner: TQBSQLScanner;
begin
  Result := TQBOracleSQLScanner.Create;
end;

function TQBOracleSelectParser.CreateWhereItems: TQBCriterions;
begin
  Result := TQBWhereItems.Create(Self, TQBCriterion);
end;

procedure TQBOracleSelectParser.DefineOperators;
var
  Operator: TQBOperator;
begin
  Operators.Clear;

  Operator := Operators.Add;
  Operator.Name := '=';
  Operator.Caption := '=';

  Operator := Operators.Add;
  Operator.Name := '!=';
  Operator.Caption := '!=';

  Operator := Operators.Add;
  Operator.Name := '<';
  Operator.Caption := '<';

  Operator := Operators.Add;
  Operator.Name := '>';
  Operator.Caption := '>';

  Operator := Operators.Add;
  Operator.Name := '<=';
  Operator.Caption := '<=';

  Operator := Operators.Add;
  Operator.Name := '>=';
  Operator.Caption := '>=';

  Operator := Operators.Add;
  Operator.Name := 'LIKE';
  Operator.Caption := 'LIKE';

  Operator := Operators.Add;
  Operator.Name := 'NOT LIKE';
  Operator.Caption := 'NOT LIKE';

  Operator := Operators.Add;
  Operator.Name := 'IN';
  Operator.Caption := 'IN';
  Operator.Prefix := '(';
  Operator.Postfix := ')';
  Operator.CommaList := true; // iplus

  Operator := Operators.Add;
  Operator.Name := 'NOT IN';
  Operator.Caption := 'NOT IN';
  Operator.Prefix := '(';
  Operator.Postfix := ')';
  Operator.CommaList := true; // iplus

  Operator := Operators.Add;
  Operator.Name := 'BETWEEN';
  Operator.Caption := 'BETWEEN';
  Operator.Relate := 'AND';
  Operator.Count := 2;

  Operator := Operators.Add;
  Operator.Name := 'NOT BETWEEN';
  Operator.Caption := 'NOT BETWEEN';
  Operator.Relate := 'AND';
  Operator.Count := 2;

  Operator := Operators.Add;
  Operator.Name := 'IS NULL';
  Operator.Caption := 'IS NULL';
  Operator.Count := 0;

  Operator := Operators.Add;
  Operator.Name := 'IS NOT NULL';
  Operator.Caption := 'IS NOT NULL';
  Operator.Count := 0;

{  Operator := Operators.Add;
  Operator.Name := '= ANY';
  Operator.Synonyms.Add('=ANY');
  Operator.Caption := 'ANY';
  Operator.Prefix := '(';
  Operator.Postfix := ')';
  Operator.CommaList := True;

  Operator := Operators.Add;
  Operator.Name := '= ALL';
  Operator.Synonyms.Add('=ALL');
  Operator.Caption := 'ALL';
  Operator.Prefix := '(';
  Operator.Postfix := ')';}

  Operator.CommaList := True;

    Operator := Operators.Add;
  Operator.Name := 'EXISTS';
  Operator.Caption := 'EXISTS';
  Operator.Prefix := '(';
  Operator.Postfix := ')';
end;

destructor TQBOracleSelectParser.Destroy;
begin
  FOracleSQLOptions.Free;
  inherited;
end;

procedure TQBOracleSelectParser.DoParse;
var
  I: integer;
  TN1, TN2, FN1, FN2: aqbString;
  T1, T2: TQBTableDef;
  Join: TQBJoinDef;
  Q: aqbChar;
  NeedInc: boolean;
  HasOracleJoins: boolean;
begin
  inherited;
  HasOracleJoins := false;
  if State <> ssError then
  begin

    //for I := 0 to TableDefs.Count - 1 do
    //  TableDefs[I].JoinDefs.Clear;

    if WhereItems.Filter <> cfAll then
      Exit;
    I := 0;
    while I < WhereItems.Count do
    begin
      NeedInc := true;
      if Scanner.IsJoinOperator(WhereItems[I].Operator) and
        (WhereItems[I].LeftSide.Kind = fkIdentifier) and
        (WhereItems[I].RightSide.Count = 1) and
        (WhereItems[I].RightSide[0].Kind = fkIdentifier) then
      begin
        Q := Scanner.IdentQuote;
        TN1 := ExtractTableName(WhereItems[I].LeftSide.Name, Q, Q);
        FN1 := ExtractFieldName(WhereItems[I].LeftSide.Name, Q, Q);
        TN2 := ExtractTableName(WhereItems[I].RightSide[0].Name, Q, Q);
        FN2 := ExtractFieldName(WhereItems[I].RightSide[0].Name, Q, Q);
        T1 := TableDefs.FindByAlias(TN1);
        T2 := TableDefs.FindByAlias(TN2);
        if (T1 <> nil) and (T2 <> nil) and (T1 <> T2) then
        begin
          if not HasOracleJoins then
          begin
            JoinDefs.Clear;
            HasOracleJoins := true;
          end;
          Join := JoinDefs.Add;
          Join.TableLeft := T1.Alias;
          Join.FieldLeftName := FN1;
          Join.TableRight := T2.Alias;
          Join.FieldRightName := FN2;
          Join.Operator := WhereItems[I].Operator;
          Join.BindJoin;
          if WhereItems[I].LeftSide.OraclePlus then
            Join.Kind := qbjROuterJoin
          else if WhereItems[I].RightSide[0].OraclePlus then
            Join.Kind := qbjLOuterJoin;
          WhereItems.Delete(I);
          NeedInc := false;
        end;
      end;
      if NeedInc then
        Inc(I);
    end;
  end;
end;

procedure TQBOracleSelectParser.DoParseAppendix;
var
  Limit: integer;
  S: TQBSelectState;
begin
  S := ssError;
  if QBCompareText(Scanner.Token, sFetch) = 0 then
  begin
    Scanner.GetNextToken;
    if QBCompareText(Scanner.Token, sFirst) = 0 then
    begin
      Scanner.GetNextToken;
      if tkInteger in Scanner.TokenKind then
      begin
        Limit := StrToInt(Scanner.Token);
        Scanner.GetNextToken;
        if ((QBCompareText(Scanner.Token, sRow) = 0) or
          (QBCompareText(Scanner.Token, sRows) = 0)) and
          (QBCompareText(Scanner.GetNextToken, sOnly) = 0) then
        begin
          FOracleSQLOptions.LimitRows := Limit;
          Scanner.GetNextToken;
          S := State;
        end;
      end
    end;
  end;
  State := S;
end;

procedure TQBOracleSelectParser.DoParseDistinct;
begin
  //
end;

function TQBOracleSelectParser.GetCaseSensitiveIdentifiers: boolean;
begin
  Result := true;
end;

function TQBOracleSelectParser.GetCommaAggregates: aqbString;
begin
  Result := OracleSQLArregates;
end;

function TQBOracleSelectParser.GetExpression: aqbString;
var
  WhereClause: aqbString;

  function GetWhereScript: aqbString;
  var
    J: integer;
    S, Join: aqbString;
  begin
    Result := WhereItems.Script;
    Join := '';
    for J := 0 to JoinDefs.Count - 1 do
    begin
      S := JoinDefs[J].Script;
      if S <> '' then
      begin
        if Join <> '' then
          Join := Join + ' AND' + sEOL;
        Join := Join + '  ' + JoinDefs[J].Script;
      end;
    end;
    if Join <> '' then
    begin
      if Result = '' then
        Result := ProcessKeyword('WHERE') + sEOL
      else
      begin
        if (Length(Result) > 1) and (Result[Length(Result) - 1] = #13) and
        (Result[Length(Result)] = #10) then
          Delete(Result, Length(Result) - 1, 2);
        Result := Result + ProcessKeyword(' AND') + sEOL;
      end;
      Result := Result + Join + sEOL;
    end;
  end;

begin
  Result := TableDefs.Script;
  if Result <> '' then
  begin
    {
    Result := ProcessKeyWord(sSelect) + ' ' + FOracleSQLOptions.DistinctScript +
      Result + GetWhereScript + GroupByItems.Script + HavingItems.Script +
      OrderByItems.Script + FOracleSQLOptions.LimitScript;
    }
    if AnsiJoin then
      WhereClause := WhereItems.Script
    else
      WhereClause := GetWhereScript;
    case TypeQuery of
      tqSelect: Result := ProcessKeyWord(sSelect) + ' ' + FOracleSQLOptions.DistinctScript +
        Result + WhereClause + GroupByItems.Script + HavingItems.Script +
          OrderByItems.Script + FOracleSQLOptions.LimitScript;
      tqInsert: Result := ProcessKeyword('INSERT') + ' ' + ProcessKeyword('INTO') +
        Result + ValuesItems.Script;
      tqUpdate: Result := ProcessKeyWord('UPDATE') + ' ' + Result + GetWhereScript;
      tqDelete: Result := ProcessKeyWord('DELETE') + ' ' + Result + GetWhereScript;
    end;
  end;
end;

function TQBOracleSelectParser.IsFullOuterJoinSupported: boolean;
begin
  Result := AnsiJoin;
end;

function TQBOracleSelectParser.IsOracleJoins: boolean;
begin
  Result := not AnsiJoin;
end;

function TQBOracleSelectParser.IsSchemaSupported: boolean;
begin
  Result := true;
end;

function TQBOracleSelectParser.NeedParentheses: boolean;
begin
  Result := false;
end;

procedure TQBOracleSelectParser.SetOracleSQLOptions(const Value: TQBOracleSQLOptions);
begin
  if FOracleSQLOptions <> Value then
    FOracleSQLOptions.Assign(Value);
end;

procedure TQBOracleSelectParser.SetAnsiJoin(Value: Boolean);
begin
  FAnsiJoin := Value;
  TQBOracleJoinDefs(JoinDefs).AnsiJoin := Value;
end;

{ TOracleQueryBuilder }

constructor TOracleQueryBuilder.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
end;

function TOracleQueryBuilder.DeleteTableDefsAliases(AText: aqbString;
  var NeedComma: Boolean; QB: TCustomQueryBuilder; var NeedJoin: Boolean): aqbString;
var
  I: Integer;
  TempStr, TableStr: aqbString;
  AList: TaqbStringList;
begin
  Result := AText;
  if TFullQueryBuilder(Self).QueryBuilders.Count = 0 then
    Exit;
  AList := TaqbStringList.Create;
  try
    for I := 0 to TFullQueryBuilder(Self).QueryBuilders.Count - 1 do
      if TFullQueryBuilder(Self).QueryBuilders[I].Data.Alias <> '' then
        AList.Add(QBUpperCase(TFullQueryBuilder(Self).QueryBuilders[I].Data.Alias));
    TempStr := Copy(QB.Parser.TableDefs.Script, 1, Pos('FROM', QB.Parser.TableDefs.Script) + 3);
    TableStr := '';
    for I := 0 to QB.Parser.TableDefs.Count - 1 do
      if (QB.Parser.TableDefs[I].Name <> '') and
        (AList.IndexOf(QB.Parser.TableDefs[I].Name) = -1) then
          if QB.Parser.TableDefs[I].Name <>
            QB.Parser.TableDefs[I].Alias then
          TableStr := TableStr + QB.Parser.TableDefs[I].Name + ' AS ' +
            QB.Parser.TableDefs[I].Alias + ','
         else
          TableStr := TableStr + QB.Parser.TableDefs[I].Name + ',';
      TableStr := Copy(TableStr, 1, Length(TableStr) - 1);
    if TableStr = '' then
      NeedComma := False;
    TempStr := TempStr + #13#10 + TableStr;
    if TableStr <> '' then
      TempStr := TempStr + #13#10;
    Result := QBStringReplace(AText, QB.Parser.TableDefs.Script, TempStr, []);
  finally
    AList.Free;
  end;
end;

function TOracleQueryBuilder.CreateParser(AParent: TPersistent): TQBSelectParser;
begin
  Result := TQBOracleSelectParser.Create(AParent);
  TQBOracleSelectParser(Result).AnsiJoin := (qbooUseAnsiJoin in FOracleOptions);
end;

procedure TOracleQueryBuilder.SetOracleOptions(const Value: TQBOracleOptions);
begin
  if FOracleOptions <> Value then
  begin
    TQBOracleSelectParser(Parser).AnsiJoin := qbooUseAnsiJoin in Value;
    FOracleOptions := Value;
  end;
end;

function TOracleQueryBuilder.GetUseNamespaces: boolean;
begin
  Result := TQBOracleSQLScanner(Parser.Scanner).UseNamespaces;
end;


procedure TOracleQueryBuilder.SetUseNamespaces(const Value: boolean);
begin
  TQBOracleSQLScanner(Parser.Scanner).UseNamespaces := Value;
end;

{ TQBOracleJoinDef }

function TQBOracleJoinDef.GetScript: aqbString;
const
  sJoinString = '%s.%s%s %s %s.%s%s';
var
  S1: aqbString;
  ALinkedTableDef: TQBTableDef;
  AFieldDef: TQBFieldDef;
  ATableName, AOwnerTableName, ALinkedTableName, AFieldName,
    ALinkedFieldName, Pl1, Pl2: aqbString;
  I: integer;
begin
  if TQBOracleJoinDefs(Collection).AnsiJoin then
    Result := inherited GetScript
  else begin
    Result := '';
    if (oTableLeft = nil) or (FieldLeftName = '') or (Trim(FieldRightName) = '') or
      (oTableRight = nil) then
      Exit;
  
    Pl1 := '';
    Pl2 := '';
    if Kind = qbjLOuterJoin then
      Pl2 := ' (+)'
    else if Kind = qbjROuterJoin then
      Pl1 := ' (+)';
  
    ALinkedTableDef := oTableRight;
    ATableName := ProcessName(ALinkedTableDef.Name);
    if ALinkedTableDef.TableDefType = qttStoredProc then
    begin
      S1 := '';
      for I := 0 to ALinkedTableDef.FieldDefs.Count - 1 do
        if ALinkedTableDef.FieldDefs[I].Scope in QBParamTypes then
        begin
          if S1 <> '' then
            S1 := S1 + ', ';
          S1 := S1 + ':' + ALinkedTableDef.FieldDefs[I].Name;
        end;
      if S1 <> '' then
        ATableName := ATableName + '(' + S1 + ')';
    end;
  
    if (oTableLeft.Alias = '') or (oTableLeft.Name = oTableLeft.Alias) then
      AOwnerTableName := oTableLeft.ProcessName(oTableLeft.Name)
    else
      AOwnerTableName := oTableLeft.ProcessAlias(oTableLeft.Alias);
    AFieldDef := oTableLeft.FieldDefs.FindByAlias(FieldLeftName);
    if AFieldDef = nil then
      AFieldDef := oTableLeft.FieldDefs.FindByName(FieldLeftName);
    if (AFieldDef <> nil) and (AFieldDef.Alias <> '') and
      (AFieldDef.Name <> AFieldDef.Alias) then
      AFieldName := oTableLeft.ProcessAlias(AFieldDef.Alias)
    else
      AFieldName := oTableLeft.ProcessName(FieldLeftName);

    if (ALinkedTableDef.Alias <> '') and
      (ALinkedTableDef.Alias <> ALinkedTableDef.Name) then
      ALinkedTableName := ALinkedTableDef.ProcessAlias(ALinkedTableDef.Alias)
    else
      ALinkedTableName := ALinkedTableDef.ProcessName(ALinkedTableDef.Name);
    AFieldDef := ALinkedTableDef.FieldDefs.FindByAlias(FieldRightName);
    if AFieldDef = nil then
      AFieldDef := ALinkedTableDef.FieldDefs.FindByName(FieldRightName);
    if (AFieldDef <> nil) and (AFieldDef.Alias <> '') and
      (AFieldDef.Name <> AFieldDef.Alias) then
      ALinkedFieldName := ALinkedTableDef.ProcessAlias(AFieldDef.Alias)
    else
      ALinkedFieldName := ALinkedTableDef.ProcessName(FieldRightName);
  
    if (AOwnerTableName <> '') and (ALinkedTableName <> '') then
      Result := QBFormat(sJoinString, [AOwnerTableName, AFieldName, Pl1, Operator,
        ALinkedTableName, ALinkedFieldName, Pl2]);
    if (Parser = nil) or Parser.ScriptFormat.AlwaysBracketConditions then
      Result := '(' + Result + ')';
  end;
end;

end.
